Syntax10.Scn.Fnt MODULE FTP; (* is 13.04.94 *) (* ARD, Sun, 24-Jul-1994 *) (* ARD, 22.12.94, Streams *) (* BD, 13.2.96, new NetSystem interface *) IMPORT Texts, Oberon, Files, Viewers, MenuViewers, TextFrames, NS := NetSystem, Input; CONST BufSize = 4100; ControlPort = 21; DataPort = 20; CR = 0DX; LF = 0AX; connect = 0; user = 1; pass = 2; command = 3; reply = 4; data = 5; end = 6; more = 7; cwd = 20; quit = 21; retr = 22; abor = 23; pwd = 24; list = 25; help = 26; noop = 27; type = 28; nlst = 29; cdup = 30; stor = 31; dele = 32; mkd = 33; rmd = 34; nocmd = 35; TYPE FTPStream = POINTER TO FTPStreamDesc; FTPStreamDesc = RECORD c: NS.Connection; R: Files.Rider END; Task = POINTER TO TaskDesc; TaskDesc = RECORD (Oberon.TaskDesc) Stream: FTPStream END; VAR W: Texts.Writer; f: Files.File; S: Texts.Reader; T: Texts.Text; V: Viewers.Viewer; Control, Data: Task; lastch, ch: CHAR; X, Y: INTEGER; pathname, name, User, Passwd: ARRAY 64 OF CHAR; buf, bufD: ARRAY BufSize OF CHAR; TypePar: ARRAY 3 OF CHAR; state, Cmd, Port, wait: INTEGER; last, len, lenD, lenF, tot, Length, OldPerc: LONGINT; WriteToFile, SetPort, SetMode, LineF, first, DosFile, RetCmd, FullDir: BOOLEAN; PROCEDURE Log(s: ARRAY OF CHAR); BEGIN Texts.WriteString(W, s); Texts.WriteLn(W); Texts.Append(T, W.buf) END Log; PROCEDURE SendCommand(cmd, arg: ARRAY OF CHAR); VAR i, j: INTEGER; BEGIN i := 0; WHILE cmd[i] # 0X DO buf[i] := cmd[i]; INC(i) END; IF arg[0] # 0X THEN buf[i] := " "; INC(i); j := 0; WHILE arg[j] # 0X DO buf[i] := arg[j]; INC(i); INC(j) END END; buf[i] := CR; buf[i+1] := LF; NS.WriteBytes(Control.Stream.c, 0, i+2, buf) END SendCommand; PROCEDURE ChangePort(port: INTEGER); VAR i, j, k: LONGINT; help: ARRAY 10 OF CHAR; adr: ARRAY 32 OF CHAR; x1, x2: INTEGER; BEGIN i := 0; k:= 0; WHILE k < LEN(NS.hostIP) DO x1:= ORD(NS.hostIP[k]); j:=0; WHILE x1> 0 DO help[j]:= CHR((x1 MOD 10) + ORD("0")); INC(j); x1:= x1 DIV 10 END; DEC(j); WHILE j >=0 DO adr[i]:= help[j]; INC(i); DEC(j) END; adr[i]:= ","; INC(k); INC(i); END; x1 := port DIV 256; x2 := port MOD 256; j := 0; WHILE x1 > 0 DO help[j] := CHR(x1 MOD 10 + ORD("0")); INC(j); x1 := x1 DIV 10 END; DEC(j); WHILE j >= 0 DO adr[i] := help[j]; INC(i); DEC(j) END; j := 0; IF x2 > 0 THEN WHILE x2 > 0 DO help[j] := CHR(x2 MOD 10 + ORD("0")); INC(j); x2 := x2 DIV 10 END; DEC(j) ELSE help[0] := "0" END; adr[i] := ","; INC(i); WHILE j >= 0 DO adr[i] := help[j]; INC(i); DEC(j) END; adr[i] := 0X; i := 0; WHILE adr[i] # 0X DO IF adr[i] = "." THEN adr[i] := "," END; INC(i) END; SendCommand("PORT", adr); END ChangePort; PROCEDURE ReadPath; VAR i: INTEGER; BEGIN i := 0; pathname[i] := 0X; WHILE (ch = " ") OR (ch = CR) OR (ch = 9X) DO Texts.Read(S, ch) END; WHILE (ch # "~") & (ch # " ") & (ch # "/") & (ch # CR) & (ch # 9X) DO pathname[i] := ch; INC(i); Texts.Read(S, ch) END; IF (ch = "/") THEN Texts.Read(S, ch); IF (ch = "d") THEN FullDir := TRUE; Texts.Read(S, ch) END END; pathname[i] := 0X; END ReadPath; PROCEDURE SearchName; VAR i, j: INTEGER; BEGIN i := 0; j := 0; WHILE (pathname[i] # 0X) DO IF (pathname[i] = "/") THEN j := i+1 END; INC(i) END; IF TRUE THEN i := 0; WHILE (pathname[j] # 0X) DO name[i] := pathname[j]; INC(i); INC(j) END; name[i] := 0X ELSE HALT(99) END END SearchName; (* Buffer handling ==============================================================================*) PROCEDURE ResIs(str: ARRAY OF CHAR): BOOLEAN; BEGIN RETURN (buf[0] = str[0]) & (buf[1] = str[1]) & (buf[2] = str[2]) END ResIs; PROCEDURE WriteText; VAR num: ARRAY 10 OF CHAR; i, j: LONGINT; BEGIN IF ResIs("150") & RetCmd THEN i := 0; RetCmd := FALSE; WHILE (buf[i] # "(" ) DO INC(i) END; j := 0; INC(i); WHILE (buf[i] # " " ) & (buf[i] # ".") DO num[j] := buf[i]; INC(i); INC(j) END; DEC(j); IF (buf[i] = ".") THEN WHILE (buf[i] # "(") DO INC(i) END; j := 0; INC(i); WHILE (buf[i] # " " ) DO num[j] := buf[i]; INC(i); INC(j) END; DEC(j) END; i := 1; Length := 0; WHILE (j >= 0) DO Length := Length + (ORD(num[j])-48)*i; i := i*10; DEC(j) END; END; Texts.WriteString(W, buf); Texts.WriteLn(W); IF (V = NIL) OR (V.state <= 0) THEN Oberon.AllocateSystemViewer(Oberon.Par.vwr.X, X, Y); V := MenuViewers.New(TextFrames.NewMenu("FTP", "System.Close"), TextFrames.NewText(T, 0), TextFrames.menuH, X, Y) END; Texts.Append(T, W.buf); END WriteText; PROCEDURE TransBufW; VAR i: LONGINT; BEGIN IF (TypePar[0] = "I") THEN i := 0; WHILE (i < lenD) DO IF (bufD[i] # LF) THEN Texts.Write(W, bufD[i]) ELSE Texts.WriteLn(W) END; INC(i) END ELSE i := last; WHILE (i < lenD) DO IF (bufD[i] # CR) THEN Texts.Write(W, bufD[i]); INC(i) ELSE Texts.WriteLn(W); INC(i,2) END END; IF (i > lenD) THEN last := i-lenD END END; Texts.Append(T, W.buf) END TransBufW; PROCEDURE TransBufF; VAR i: INTEGER; Perc: LONGINT; BEGIN i := 0; WHILE (i < lenD) DO IF (bufD[i] # LF) & (bufD[i] # CR) THEN Files.Write(Data.Stream.R, bufD[i]); lastch := bufD[i]; INC(tot) ELSIF (bufD[i] = CR) THEN Files.Write(Data.Stream.R, bufD[i]); lastch := bufD[i]; INC(tot) ELSIF (bufD[i] = LF) & (lastch # CR) THEN Files.Write(Data.Stream.R, CR); lastch := bufD[i]; INC(tot) END; INC(i) END; Perc := ENTIER(tot*100/Length); IF (Perc MOD 5 = 0) & (Perc # OldPerc) THEN OldPerc := Perc; Texts.WriteInt(W, Perc, 5); Texts.WriteString(W," %"); IF (Perc = 50) THEN Texts.WriteLn(W) END; Texts.Append(T, W.buf) END TransBufF; PROCEDURE SendFile(SendType: CHAR); VAR x: CHAR; len, Perc: LONGINT; BEGIN IF ~Data.Stream.R.eof THEN IF (SendType = "A") THEN lenD := 0; IF LineF THEN bufD[lenD] := LF; INC(lenD); LineF := FALSE END; Files.Read(Data.Stream.R, x); WHILE ~Data.Stream.R.eof & (lenD < BufSize) DO bufD[lenD] := x; INC(lenD); IF (x = CR) & (lenD < BufSize) THEN bufD[lenD] := LF; INC(lenD) ELSIF (x = CR) & (lenD = BufSize) THEN LineF := TRUE END; Files.Read(Data.Stream.R, x); END; NS.WriteBytes(Data.Stream.c, 0, lenD, bufD); ELSE (* Image File *) len := lenF - Files.Pos(Data.Stream.R); IF (len <= BufSize) THEN lenD := SHORT(len) ELSE lenD := BufSize END; Files.ReadBytes(Data.Stream.R, bufD, lenD); NS.WriteBytes(Data.Stream.c, 0, lenD, bufD); IF (len <= BufSize) THEN Files.Read(Data.Stream.R, x) END; END; Perc := ENTIER(100*Files.Pos(Data.Stream.R)/lenF); IF (Perc MOD 5 = 0) & (Perc # OldPerc) THEN OldPerc := Perc; Texts.WriteInt(W, Perc, 5); Texts.WriteString(W," %"); IF (Perc = 50) THEN Texts.WriteLn(W) END; Texts.Append(T, W.buf) END ELSE IF ((SendType = "I") OR (SendType = "A")) & ~LineF THEN (* Timeout *) NS.CloseConnection(Data.Stream.c); Oberon.Remove(Data); Data := NIL; state := reply; f := NIL; Log(" file sent"); ReadPath; IF pathname # "" THEN Cmd := stor END END END SendFile; (* Send & Receive handlers =========================================================================*) PROCEDURE Receive; VAR Perc: LONGINT; res: INTEGER; newC: NS.Connection; BEGIN INC(Data.time, Input.TimeUnit DIV 4); IF NS.Requested(Data.Stream.c) THEN NS.Accept(Data.Stream.c, newC, res); NS.CloseConnection(Data.Stream.c); Data.Stream.c:= newC; ELSIF (NS.Available(Data.Stream.c) > 0) THEN (* established *) lenD := NS.Available(Data.Stream.c); IF lenD > 0 THEN IF lenD > BufSize THEN lenD := BufSize END; NS.ReadBytes(Data.Stream.c, 0, lenD, bufD); IF ~WriteToFile THEN TransBufW ELSIF (TypePar[0] = "A") THEN TransBufF (* ASCII File *) ELSE Files.WriteBytes(Data.Stream.R, bufD, lenD); tot := tot+lenD; Perc := ENTIER(100*tot/Length); (* Image File *) IF (Perc MOD 5 = 0) & (Perc # OldPerc) THEN OldPerc := Perc; Texts.WriteInt(W,Perc,5); Texts.WriteString(W," %"); IF (Perc = 50) THEN Texts.WriteLn(W) END; Texts.Append(T, W.buf) END END END; ELSIF (NS.Available(Control.Stream.c) > 0) & (NS.Available(Data.Stream.c) = 0) THEN NS.CloseConnection(Data.Stream.c); Oberon.Remove(Data); Data := NIL; state := reply; IF WriteToFile THEN Texts.WriteLn(W) END; Texts.Append(T, W.buf); IF (f # NIL) THEN Files.Register(f); f := NIL; tot := 0 END; ReadPath; IF pathname # "" THEN Cmd := retr END END Receive; PROCEDURE Send; VAR res: INTEGER; newC: NS.Connection; BEGIN INC(Data.time, Input.TimeUnit DIV 4); IF NS.Requested(Data.Stream.c) THEN NS.Accept(Data.Stream.c, newC, res); NS.CloseConnection(Data.Stream.c); Data.Stream.c:= newC; ELSIF ((NS.State(Data.Stream.c) = NS.inout) OR (NS.State(Data.Stream.c) = NS.out)) THEN SendFile(TypePar[0]) END (* established *) END Send; PROCEDURE Handle; VAR res: INTEGER; BEGIN INC(Control.time, Input.TimeUnit DIV 4); CASE state OF connect: IF NS.Available(Control.Stream.c) >= 3 THEN NS.ReadString(Control.Stream.c, buf); WriteText; WHILE buf[3] = "-" DO NS.ReadString(Control.Stream.c, buf); WriteText END; IF ResIs("220") THEN SendCommand("USER", User); state := user ELSIF ResIs("120") THEN (* wait *) ELSE SendCommand("QUIT", ""); state := end END END| user: IF NS.Available(Control.Stream.c) >= 3 THEN NS.ReadString(Control.Stream.c, buf); WriteText; WHILE buf[3] = "-" DO NS.ReadString(Control.Stream.c, buf); WriteText END; IF ResIs("230") THEN state := command ELSIF ResIs("331") THEN SendCommand("PASS", Passwd); state := pass ELSIF ResIs("530") THEN Log("Login refused"); SendCommand("QUIT", ""); state := end ELSE SendCommand("QUIT", ""); state := end END END| pass: IF NS.Available(Control.Stream.c) >= 3 THEN NS.ReadString(Control.Stream.c, buf); WriteText; IF (buf[3] = "-") THEN state := more; first := FALSE ELSIF ResIs("230") THEN state := command ELSIF ResIs("530") THEN Log("Login refused"); SendCommand("QUIT", ""); state := end ELSE SendCommand("QUIT", ""); state := end END END| command: CASE Cmd OF pwd: SendCommand("PWD",""); state := reply; Cmd := nocmd| type: SendCommand("TYPE", TypePar); state := reply; Cmd := nocmd| cwd: SendCommand("CWD", pathname); state := reply; Cmd := nocmd| cdup: SendCommand("CDUP", ""); state := reply; Cmd := nocmd| mkd: SendCommand("MKD", pathname); state := reply; Cmd := nocmd| rmd: SendCommand("RMD", pathname); state := reply; Cmd := nocmd| help: SendCommand("HELP", name); state := reply; Cmd := nocmd| abor: SendCommand("ABOR", ""); state := reply; Cmd := nocmd| noop: SendCommand("NOOP", ""); state := reply; Cmd := nocmd| quit: SendCommand("QUIT", ""); state := end; Cmd := nocmd| list, nlst: IF ~SetPort THEN NEW(Data); NEW(Data.Stream); REPEAT INC(Port); NS.OpenConnection(Data.Stream.c, Port, NS.anyIP, NS.anyport, res) UNTIL res = NS.done; ChangePort(Port); SetPort := TRUE; state := reply ELSE Data.handle := Receive; SetPort := FALSE; last := 0; IF Cmd = list THEN SendCommand("LIST", pathname) ELSE SendCommand("NLST", pathname) END; WriteToFile := FALSE; state := reply; Cmd := nocmd; END| dele: SendCommand("DELE", pathname); state := reply; ReadPath; IF pathname # "" THEN Cmd := dele ELSE Cmd := nocmd END| retr: IF ~SetPort THEN NEW(Data); NEW(Data.Stream); REPEAT INC(Port); NS.OpenConnection(Data.Stream.c, Port, NS.anyIP, NS.anyport, res) UNTIL res = NS.done; ChangePort(Port); SetPort := TRUE; state := reply ELSIF ~SetMode THEN SendCommand("TYPE", TypePar); SetMode := TRUE; state := reply ELSE Data.handle := Receive; OldPerc := 0; SetPort := FALSE; SetMode := FALSE; last := 0; lastch := 0X; tot := 0; SendCommand("RETR", pathname); SearchName; (* IF DosFile THEN f := Files.NewDOS(Path) ELSE *) f := Files.New(name); (* END; *) Files.Set(Data.Stream.R, f, 0); WriteToFile := TRUE; state := reply; Cmd := nocmd; RetCmd := TRUE END| stor: IF ~SetPort THEN NEW(Data); NEW(Data.Stream); REPEAT INC(Port); NS.OpenConnection(Data.Stream.c, Port, NS.anyIP, NS.anyport, res) UNTIL res = NS.done; ChangePort(Port); SetPort := TRUE; state := reply ELSIF ~SetMode THEN SendCommand("TYPE", TypePar); SetMode := TRUE; state := reply ELSE Data.handle := Send; OldPerc := 0; SetPort := FALSE; SetMode := FALSE; LineF := FALSE; SearchName; f := Files.Old(pathname); IF (f # NIL) THEN SendCommand("STOR", pathname); lenF := Files.Length(f); Files.Set(Data.Stream.R, f, 0); state := reply; Cmd := nocmd ELSE Log(" file not found"); NS.CloseConnection(Data.Stream.c); state := command; Cmd := nocmd END END| ELSE END| reply: IF NS.Available(Control.Stream.c) >= 3 THEN len := NS.Available(Control.Stream.c); NS.ReadString(Control.Stream.c, buf); wait := 1000; WriteText; IF (buf[3] = "-") THEN state := more; first := FALSE ELSIF ResIs("257") OR ResIs("226") OR ResIs("250") OR ResIs("200") OR ResIs("225") THEN state := command ELSIF ResIs("150") THEN state := data; Oberon.Install(Data) ELSIF ResIs("421") THEN SendCommand("QUIT", ""); state := end; ELSE state := command; IF (Data # NIL) & (Data.Stream.c # NIL) THEN NS.CloseConnection(Data.Stream.c); Data := NIL END END END| more: IF NS.Available(Control.Stream.c) >= 3 THEN NS.ReadString(Control.Stream.c, buf); wait := 1000; first := TRUE; WriteText; state := more ELSIF first & ResIs("530") THEN SendCommand("QUIT", ""); state := end ELSIF first THEN DEC(wait); IF (wait = 0) THEN state := command END END| data: IF (Cmd = abor) THEN SendCommand("ABOR", ""); state := reply; Cmd := nocmd END| end: REPEAT UNTIL (NS.Available(Control.Stream.c) > 0); NS.ReadString(Control.Stream.c, buf); WriteText; NS.CloseConnection(Control.Stream.c); Log(" FTP Stopped"); Oberon.Remove(Control); Control := NIL| END END Handle; (* Command procedures =======================================================================*) PROCEDURE Connect*; VAR S: Texts.Scanner; res: INTEGER; remoteIP: NS.IPAdr; BEGIN IF Control = NIL THEN Texts.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos); f := NIL; Texts.Scan(S); IF (S.class = Texts.Name) OR (S.class = Texts.String) THEN SetPort := FALSE; SetMode := FALSE; TypePar := "A "; state := connect; NEW(Control); NEW(Control.Stream); Log(" FTP (ARD/IS, 9. 1. 95) "); Log(" trying to open connection..."); NS.GetIP(S.s, remoteIP); NS.OpenConnection(Control.Stream.c, NS.anyport, remoteIP, ControlPort, res); COPY(NS.user, User); COPY(NS.passwd, Passwd); IF res = NS.done THEN Texts.Scan(S); IF (S.class = Texts.Name) THEN COPY(S.s, User); Texts.Scan(S); Texts.Scan(S); IF (S.class = Texts.Name) THEN COPY(S.s, Passwd) END END; Control.handle := Handle; Oberon.Install(Control) ELSIF res = NS.timeout THEN Log("Connect timed out") ELSE Log("Not Done") END ELSE Log("Invalid name") END ELSE Log("Already connected") END END Connect; PROCEDURE Start*; BEGIN NS.Start; END Start; PROCEDURE Stop*; (* only in desperate case *) BEGIN IF (Data # NIL) THEN IF (Data.Stream.c # NIL) THEN NS.CloseConnection(Data.Stream.c) END; Oberon.Remove(Data); Data := NIL END; IF (Control # NIL) THEN IF (Control.Stream.c # NIL) THEN NS.CloseConnection(Control.Stream.c) END; Oberon.Remove(Control); Control := NIL; END; Log("FTP abnormally stopped") END Stop; PROCEDURE SetCmd(cmd: INTEGER; txt: ARRAY OF CHAR); BEGIN IF (state = command) OR (txt = "FTP.Abort") THEN Cmd := cmd; Log(txt) ELSE Log("previous command not accomplished") END END SetCmd; PROCEDURE Clear*; BEGIN SetCmd(nocmd, "FTP.Clear") END Clear; PROCEDURE CurrentDir*; BEGIN SetCmd(pwd, "FTP.CurrentDir") END CurrentDir; PROCEDURE Disconnect*; BEGIN SetCmd(quit, "FTP.Disconnect") END Disconnect; PROCEDURE Abort*; BEGIN SetCmd(abor, "FTP.Abort") END Abort; PROCEDURE Check*; BEGIN SetCmd(noop, "FTP.Check") END Check; PROCEDURE Directory*; BEGIN IF state = command THEN Log("FTP.Directory"); Texts.OpenReader(S, Oberon.Par.text, Oberon.Par.pos); Texts.Read(S, ch); FullDir := FALSE; ReadPath; DosFile := FALSE; IF FullDir THEN Cmd := list ELSE Cmd := nlst END ELSE Log("previous command not accomplished") END END Directory; PROCEDURE ChangeDir*; BEGIN IF state = command THEN Log("FTP.ChangeDir"); Texts.OpenReader(S, Oberon.Par.text, Oberon.Par.pos); Texts.Read(S, ch); ReadPath; IF pathname = ".." THEN Cmd := cdup ELSE Cmd := cwd END ELSE Log("previous command not accomplished") END END ChangeDir; PROCEDURE GetArg(cmd: INTEGER; txt: ARRAY OF CHAR); BEGIN IF state = command THEN Log(txt); Texts.OpenReader(S, Oberon.Par.text, Oberon.Par.pos); Texts.Read(S, ch); ReadPath; Cmd := cmd ELSE Log("previous command not accomplished") END END GetArg; PROCEDURE MakeDir*; BEGIN GetArg(mkd, "FTP.MakeDir") END MakeDir; PROCEDURE RemoveDir*; BEGIN GetArg(rmd, "FTP.RemoveDir") END RemoveDir; PROCEDURE DeleteFile*; BEGIN GetArg(dele, "FTP.DeleteFile") END DeleteFile; PROCEDURE SetType*; BEGIN GetArg(type, "FTP.SetType"); COPY(pathname, TypePar) END SetType; PROCEDURE RetrieveFile*; BEGIN GetArg(retr, "FTP.RetrieveFile"); DosFile := FALSE; TypePar[0] := "I"; TypePar[1] := 0X END RetrieveFile; PROCEDURE RetrieveText*; BEGIN GetArg(retr, "FTP.RetrieveText"); DosFile := FALSE; TypePar[0] := "A"; TypePar[1] := 0X END RetrieveText; PROCEDURE RetrieveDOSFile*; BEGIN GetArg(retr, "FTP.RetrieveDOSFile"); DosFile := TRUE; TypePar[0] := "I"; TypePar[1] := 0X END RetrieveDOSFile; PROCEDURE RetrieveDOSText*; BEGIN GetArg(retr, "FTP.RetrieveDOSText"); DosFile := TRUE; TypePar[0] := "A"; TypePar[1] := 0X END RetrieveDOSText; PROCEDURE StoreFile*; BEGIN GetArg(stor, "FTP.StoreFile"); TypePar[0] := "I"; TypePar[1] := 0X END StoreFile; PROCEDURE StoreText*; BEGIN GetArg(stor, "FTP.StoreText"); TypePar[0] := "A"; TypePar[1] := 0X END StoreText; PROCEDURE Help*; VAR scan: Texts.Scanner; beg, end, time: LONGINT; text: Texts.Text; BEGIN IF state = command THEN Texts.OpenScanner(scan, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(scan); IF (scan.class = Texts.Char) & (scan.c = "^") THEN Oberon.GetSelection(text, beg, end, time); IF (time >= 0) THEN Texts.OpenScanner(scan, text, beg); Texts.Scan(scan) END; END; IF (scan.class = Texts.Name) OR (scan.class = Texts.String) THEN COPY(scan.s, name); Cmd := help END END Help; PROCEDURE ClearLog*; VAR F: TextFrames.Frame; BEGIN IF (V.dsc # NIL) & (V.dsc.next IS TextFrames.Frame) THEN F := V.dsc.next(TextFrames.Frame); Texts.Delete(F.text, 0, F.text.len) END; END ClearLog; BEGIN Control := NIL; Data := NIL; Port := 1499; RetCmd := FALSE; T := TextFrames.Text(""); Texts.OpenWriter(W); Oberon.AllocateSystemViewer(Oberon.Mouse.X, X, Y); V := MenuViewers.New(TextFrames.NewMenu("FTP", "System.Close FTP.ClearLog"), TextFrames.NewText(T, 0), TextFrames.menuH, X, Y); END FTP.